/*  vegas.c - main source file
 */

/*
    Las Vegas Video Poker, a Video Poker Game for the Psion Series 3
    Version 1.1a
    Copyright (C) 1993  J Cade Roux

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 * See the file COPYING for the GNU General Public License.
 * Mail: Cade Roux
 *                      c/o Dubroca
 *                      Box 513
 *                      Boutte, LA  70039
 *                      USA
 */

#include <plib.h>
#include <wlib.h>
#include <hwif.h>

#include <utils.h>

#include "global.h"
#include "vegas.h"

#define NUM_SUITS					4
#define NUM_RANKS					13
#define DECK_SIZE					52
#define MIN_PAYOUT_RANK			10		/* Jack */
#define NUM_CARDS					5
#define DRAW_LIMIT				NUM_CARDS
#define NUM_PAYOUTS				10

#define HORIZ_CARD_SPACING		6
#define CARD_Y						5
#define CARD_X						(HORIZ_CARD_SPACING / 2)
#define TEXT_Y						2
#define TEXT_X						2
#define DESIGN_WIDTH				24
#define DESIGN_HEIGHT			24
#define DESIGN_Y					18
#define DESIGN_X					8
#define HIGHLIGHT_GAP			2

enum {	LOSE = 0,
			PAIR,
			TWO_PAIRS,
			THREE_OF_A_KIND,
			STRAIGHT,
			FLUSH,
			FULL_HOUSE,
			FOUR_OF_A_KIND,
			STRAIGHT_FLUSH,
			ROYAL_FLUSH } ;

typedef struct {
	UINT		mult ;
	TEXT *	str ;
} PAYOUT ;

LOCAL_D H_MENU_DATA		mdata[] = {	"Vegas Poker", 2,
												NULL } ;

LOCAL_D TEXT *				cmds[] = {	"nNew Game",
												"xExit",
												NULL } ;

GLREF_D WSERV_SPEC *		wserv_channel ;
GLREF_D TEXT *				DatCommandPtr ;
GLDEF_D TEXT **			_cmds = cmds ;
GLDEF_D H_MENU_DATA *	_mdata = mdata ;

LOCAL_D struct {
	PAYOUT *		payouts ;
	TEXT *		card_names ;
	ULONG			seedl ;
	BOOL			new_game ;
	BOOL			quit ;
	INT			card_width ;
	INT			card_height ;
	INT			wallet_y ;
	INT			wallet_width ;
	INT			stake_width ;
	INT			text_ascent ;
} status = { NULL, NULL } ;

LOCAL_D struct {
	UINT  win ;
	INT   gc ;
	UINT	bmp ;
	UINT	cardback_id ;
} gfx ;

LOCAL_D struct {
	LONG wallet ;
	LONG stake ;
} money ;

LOCAL_D BYTE		hand[ NUM_CARDS + DRAW_LIMIT ] ;

#define UP_MASK			0x80
#define VAL_MASK			0x7F
#define FACE_UP(x)		((hand[x] & UP_MASK) != 0)
#define CARD_INDEX(x)	(hand[x] & VAL_MASK)
#define CARD_RANK(x)		((CARD_INDEX(x)) % NUM_RANKS)
#define CARD_SUIT(x)		((CARD_INDEX(x)) / NUM_RANKS)

/* Return TRUE if card with index is dealt in hand already */
LOCAL_C BOOL InHand(INT index, INT cards_to_check) {
	for ( cards_to_check-- ; cards_to_check >= 0 ; cards_to_check-- )
		if ( CARD_INDEX(cards_to_check) == index )
			return(TRUE) ;
	return(FALSE) ;
}

/* Deal NUM_CARDS + DRAW_LIMIT cards into hand */
LOCAL_C VOID Deal(VOID) {
	INT	num_dealt ;
	INT	index ;
	INT	count ;

	for (	num_dealt = 0 ;
			num_dealt < NUM_CARDS + DRAW_LIMIT ;
			num_dealt++ )  {

		for ( count = 0, index = Random(&status.seedl, 0, DECK_SIZE - num_dealt - 1) ;
				index >= 0 ;
				count++ ) {
			if ( !InHand(count, num_dealt) )
				index-- ;
		}

		hand[num_dealt] = (--count) | UP_MASK ;
	}
}

/* Return the payout category of the hand */
LOCAL_C UINT Payout(VOID) {
	BOOL	straight ;
	BOOL	three ;
	INT	iLp ;
	INT	num_in_suit[NUM_SUITS] ;
	INT	num_in_rank[NUM_RANKS] ;
	INT	pairs ;
	INT	num_consec ;

	/* Initialize arrays to 0 */
	for ( iLp = 0 ; iLp < NUM_SUITS ; num_in_suit[iLp++] = 0 ) ;
	for ( iLp = 0 ; iLp < NUM_RANKS ; num_in_rank[iLp++] = 0 ) ;

	/* Calculate the number of cards in each suit and each rank */
	for (	iLp = 0 ;
			iLp < NUM_CARDS ;
			iLp++ ) {
		++num_in_rank[CARD_RANK(iLp)] ;
		++num_in_suit[CARD_SUIT(iLp)] ;
	}

	/* Calculate the number of consecutive cards */
	for ( iLp = 0 ; !num_in_rank[iLp] ; iLp++ ) ;
	for (	num_consec = 0 ;
			iLp < NUM_RANKS && num_in_rank[iLp] ;
			num_consec++, iLp++ ) ;

	straight = (	num_consec == NUM_CARDS ||
						(	num_in_rank[0] &&
							num_in_rank[9] &&
							num_in_rank[10] &&
							num_in_rank[11] &&
							num_in_rank[12] ) ) ;

	/* Check for a flush */
	for (	iLp = 0 ; iLp < NUM_SUITS ; iLp++ )
		if ( num_in_suit[iLp] == NUM_CARDS ) {
			if ( straight )
				return(	num_in_rank[0] && num_in_rank[12] ?
							ROYAL_FLUSH :
							STRAIGHT_FLUSH ) ;
			else
				return(FLUSH) ;
		}

	if ( straight )
		return(STRAIGHT) ;

	for (	three = FALSE, pairs = 0, iLp = 0 ;
			iLp < NUM_RANKS ;
			iLp++ ) {
		if ( num_in_rank[iLp] == 4 )
			return(FOUR_OF_A_KIND) ;
		three |= num_in_rank[iLp] == 3 ;
		pairs += num_in_rank[iLp] == 2 ;
		if ( three && pairs )
			return(FULL_HOUSE) ;
	}

	if ( three )
		return(THREE_OF_A_KIND) ;
	else if ( pairs == 2 )
		return(TWO_PAIRS) ;
	else if ( pairs ) {
		if ( num_in_rank[0] >= 2 )
			return(PAIR) ;
		for (	iLp = MIN_PAYOUT_RANK ; iLp < NUM_RANKS ; iLp++ )
			if ( num_in_rank[iLp] >= 2 )
				return(PAIR) ;
	}

	return(LOSE) ;
}

/* Clear the screen */
LOCAL_C VOID ClearScreen(VOID) {
	P_RECT rect = { {0, 0} } ;

	rect.br = wserv_channel->conn.info.pixels ;
	gClrRect(&rect, G_TRMODE_CLR) ;
}

/* Invert the cursor box */
LOCAL_C VOID DrawBox(INT pos) {
	P_RECT rect = { {0, 0} } ;

	rect.br.x = status.card_width ;
	rect.br.y = status.card_height ;
	p_offrec(	&rect,
					CARD_X + pos * (status.card_width + HORIZ_CARD_SPACING),
					CARD_Y ) ;
	p_insrec(&rect, -HIGHLIGHT_GAP, -HIGHLIGHT_GAP) ;
	gDrawBox(&rect) ;
}

/* Draw the image of the card at pos in the hand */
LOCAL_C VOID CardImage(INT pos) {
	P_RECT rect = { {0, 0} } ;
	P_RECT srcrect = { {0, 0}, {DESIGN_WIDTH, DESIGN_HEIGHT} } ;

	rect.br.x = status.card_width ;
	rect.br.y = status.card_height ;
	p_offrec(	&rect,
					CARD_X + pos * (status.card_width + HORIZ_CARD_SPACING),
					CARD_Y ) ;

	gClrRect(&rect, G_TRMODE_CLR) ;
	gBorderRect(&rect, W_BORD_CORNER_1) ;
	p_insrec(&rect, 1, 1) ;

	if ( FACE_UP(pos) ) {
		gPrintText(	rect.tl.x + TEXT_X,
						rect.tl.y + status.text_ascent + TEXT_Y,
						&status.card_names[2 * CARD_RANK(pos)],
						1 + (CARD_RANK(pos) == 9) ) ;

		rect.tl.x += DESIGN_X ;
		rect.tl.y += DESIGN_Y ;
		p_offrec(&srcrect, (CARD_SUIT(pos) + 1) * DESIGN_WIDTH, 0) ;
		gCopyBit(&rect.tl, gfx.bmp, &srcrect, G_TRMODE_SET) ;
	}
	else
		gFillPattern(&rect, gfx.cardback_id, G_TRMODE_SET) ;
}

/* Redraw all of the cards */
LOCAL_C VOID RedrawAllCards(VOID) {
	INT iLp ;

	for ( iLp = 0 ; iLp < NUM_CARDS ; CardImage(iLp++) ) ;
}

/* Toggle the card at pos */
LOCAL_C VOID Toggle(INT pos) {
	if ( FACE_UP(pos) )
		hand[pos] &= VAL_MASK ;
	else
		hand[pos] |= UP_MASK ;

	CardImage(pos) ;
}

/* Update the money at the bottom of the screen */
LOCAL_C VOID DisplayMoney(VOID) {
	TEXT *		str = NULL ;
	TEXT			txt[25] ;
	P_RECT		wallet_rect ;
	P_RECT		stake_rect ;

	wallet_rect.tl.x = 0 ;
	wallet_rect.tl.y = status.wallet_y - status.text_ascent ;
	wallet_rect.br.x = status.wallet_width ;
	wallet_rect.br.y = status.wallet_y ;

	stake_rect.tl.x = status.wallet_width ;
	stake_rect.tl.y = wallet_rect.tl.y ;
	stake_rect.br = wserv_channel->conn.info.pixels ;

	p_atos(txt, ReadResource(RS_WALLET, &str), money.wallet) ;
	gPrintBoxText(&wallet_rect, status.text_ascent, G_TEXT_ALIGN_LEFT, 0, txt, p_slen(txt)) ;

	p_atos(txt, ReadResource(RS_STAKE, &str), money.stake) ;
	gPrintBoxText(&stake_rect, status.text_ascent, G_TEXT_ALIGN_LEFT, 0, txt, p_slen(txt)) ;

	p_free(str) ;
}

/* Prompt for a new stake */
LOCAL_C VOID SetStake(VOID) {
	TEXT 				bcs[25] ;
	TEXT *			str = NULL ;
	H_DI_NUMBER		dialog_num = { NULL, 1L, 1L } ;
	H_DI_TEXT		dialog_text = { NULL, H_DTEXT_ALIGN_CENTRE } ;

	dialog_num.value = &money.stake ;
	dialog_num.high = money.wallet ;
	p_atos(&bcs[1], ReadResource(RS_YOU_HAVE, &str), money.wallet) ;
	p_free(str) ;
	bcs[0] = p_slen(&bcs[1]) ;
	dialog_text.str = bcs ;

	Res_OpenDialog(RS_SET_STAKE) ;
	if ( uAddDialogItem(H_DIALOG_TEXT, NULL, &dialog_text) )
		p_leave(E_UTILS_DIALOG) ;
	Res_AddDialogItem(H_DIALOG_NUMBER, RS_CURRENCY, &dialog_num) ;

	if( uRunDialog() < 0 )
		p_leave(E_UTILS_DIALOG) ;

	money.wallet -= money.stake ;
}

/* Execute a menu command */
LOCAL_C VOID ExecuteCommand(INT keycode) {
	keycode = uLocateCommand(keycode) ;
	if ( keycode >= 0 )
		switch ( keycode ) {

			case 0 :
				Check( p_enter5( Query, RS_NEW_GAME, RS_YES, RS_NO, &status.new_game) ) ;
				break ;

			case 1 :
				Check( p_enter5( Query, RS_QUIT, RS_YES, RS_NO, &status.quit) ) ;
				break ;

			default :
				break ;
	}
}

/* Draw a suitable number of cards and score the hand */
LOCAL_C VOID DrawAndScoreHand(VOID) {
	INT		iLp ;
	INT		count ;
	UINT		hand_class ;
	TEXT		buf[40] ;
	TEXT *	str = NULL ;

	/* Replace face down cards with new cards */
	for (	count = 0, iLp = 0 ;
			count < DRAW_LIMIT && iLp < NUM_CARDS ;
			iLp++ )
		if ( !FACE_UP(iLp) )
			hand[iLp] = hand[NUM_CARDS + count++] ;

	hand_class = Payout() ;
	money.wallet += status.payouts[hand_class].mult * money.stake ;
	RedrawAllCards() ;
	DisplayMoney() ;

	if ( hand_class != LOSE ) {
		p_atos(	buf, ReadResource(RS_WINNINGS, &str),
					(LONG) (status.payouts[hand_class].mult * money.stake),
					status.payouts[hand_class].mult,
					money.stake) ;
		uDisplayText(	status.payouts[hand_class].str,
							buf,
							NULL ) ;
	}
	else
		uDisplayText(	status.payouts[hand_class].str,
							ReadResource(RS_NO_WINNINGS, &str),
							NULL ) ;

	p_free(str) ;
}

#pragma save, ENTER_CALL

/* Play a single game */
LOCAL_C INT MainLoop(VOID) {
	WMSG_KEY		key ;
	INT			curr_pos ;
	INT			iLp ;
	INT			ret ;

	money.wallet = 100L ;
	money.stake = 5L ;
	status.new_game = FALSE ;
	status.seedl = p_date() ;

	do {

		SetStake() ;
		ClearScreen() ;
		DisplayMoney() ;
		Deal() ;
		RedrawAllCards() ;
		DrawBox(curr_pos = 0) ;

		do {

			uGetKey(&key) ;

			if ( key.keycode == CONS_EVENT_COMMAND )
				status.quit = TRUE ;
			else if ( key.keycode & W_SPECIAL_KEY )
				ExecuteCommand(key.keycode & ~W_SPECIAL_KEY) ;
			switch ( key.keycode ) {

				case W_KEY_HELP :
					if ( key.modifiers == W_CTRL_MODIFIER )
						SystemHelp() ;
					else
						Help(RH_MAIN, RH_MAIN) ;
					break ;

				case W_KEY_MENU :
					if ( (ret = uPresentMenus()) > 0 )
						ExecuteCommand(ret) ;
					break ;

				case W_KEY_LEFT :
					DrawBox(curr_pos) ;
					DrawBox(curr_pos = ((curr_pos == 0) ? NUM_CARDS - 1 : curr_pos - 1)) ;
					break ;

				case W_KEY_RIGHT :
					DrawBox(curr_pos) ;
					DrawBox(curr_pos = ((curr_pos == NUM_CARDS - 1) ? 0 : curr_pos + 1)) ;
					break ;

				case '*' :
					for (	iLp = 0 ;
							iLp < NUM_CARDS ;
							hand[iLp++] |= UP_MASK ) ;
					RedrawAllCards() ;
					break ;

				case '/' :
					for (	iLp = 0 ;
							iLp < NUM_CARDS ;
							hand[iLp++] &= VAL_MASK ) ;
					RedrawAllCards() ;
					break ;

				case '.' :
					for (	iLp = 0 ;
							iLp < NUM_CARDS ;
							Toggle(iLp++) ) ;
					break ;

				case ' ' :
					Toggle(curr_pos) ;
					break ;

				default :
					if (	key.keycode >= '1' &&
							key.keycode <= '5' )
						Toggle(key.keycode - '1') ;
					break ;

			}

		} while (	key.keycode != W_KEY_RETURN &&
						!status.new_game &&
						!status.quit ) ;

		if ( status.new_game || status.quit )
			break ;

		DrawAndScoreHand() ;

		if ( !money.wallet ) {
			Check( p_enter5(Query, RS_WALLET_EMPTY, RS_YES, RS_NO, &status.new_game) ) ;
			status.quit = !status.new_game ;
		}

	} while ( !status.new_game && !status.quit ) ;

	return(E_APP_NONE) ;
}

LOCAL_C INT SpecificInit(VOID) {
	P_POINT				bit_size ;
	UINT					field_set =	G_GC_MASK_GMODE | G_GC_MASK_TEXTMODE |
											G_GC_MASK_STYLE | G_GC_MASK_FONT ;
	G_GC					g_gc = {	G_TRMODE_INV,
										G_TRMODE_REPL,
										G_STY_DOUBLE,
										0,
										WS_FONT_BASE + 1 } ;
	INT					iLp ;
	G_FONT_INFO			font_info ;

	hCrackCommandLine() ;
	p_unmarka() ;

	OpenResource(DatCommandPtr) ;

	gfx.win = uFindMainWid() ;
	gfx.bmp = gOpenBit(DatCommandPtr, 0, 0, &bit_size) ;
	gfx.cardback_id = gOpenBit(DatCommandPtr, 1, 0, &bit_size) ;

	gfx.gc = gCreateGC(gfx.win, field_set, &g_gc) ;
	
	status.payouts = f_alloc(NUM_PAYOUTS * sizeof(PAYOUT)) ;

	for ( iLp = 0 ; iLp < NUM_PAYOUTS ; iLp++ ) {
		status.payouts[iLp].str = NULL ;
		ReadResource_buf(RS_PAYOUT0 + iLp, &status.payouts[iLp].mult) ;
		ReadResource(RS_HAND0 + iLp, &status.payouts[iLp].str) ;
	}

	ReadResource(RS_CARD_NAMES, &status.card_names) ;

	gFontInfo( WS_FONT_BASE + 1, G_STY_DOUBLE, &font_info) ;
	status.text_ascent = font_info.ascent ;
	status.card_width = wserv_channel->conn.info.pixels.x / NUM_CARDS - HORIZ_CARD_SPACING ;
	status.card_height = wserv_channel->conn.info.pixels.y - status.text_ascent - 2 * CARD_Y ;
	status.wallet_y = wserv_channel->conn.info.pixels.y ;
	status.wallet_width = wserv_channel->conn.info.pixels.x / 2 ;
	status.stake_width = wserv_channel->conn.info.pixels.x - status.wallet_width ;

	Help(RH_ABOUT, RH_MAIN) ;

	return(E_APP_NONE) ;
}

#pragma restore

GLDEF_C VOID main(VOID) {
	uCommonInit() ;
	wDisableLeaves(FALSE) ;
	Check( p_enter1(SpecificInit) ) ;
	do {
		Check( p_enter1(MainLoop) ) ;
	} while ( !status.quit ) ;
	p_exit(0) ;
}
